feat(task-board): delivery lanes behind a default-off org flag - #6457
Merged
Merged
Conversation
Adds Approved, Merged and Post-deploy Validation between In Review and Done, gated by a default-off org flag `delivery_lanes_enabled`. Every automatic ship path reads one function, `shippedLane(flags)`, so "flag off = zero behaviour change" is a single tested fact rather than five call sites agreeing by luck. Rebased onto main after 290 commits, with four fixes folded in (see the PR description for what changed and why they were not left as follow-ups): The e2e asserted `expect(shipError).not.toMatch(...)` on a value that is `null` when the ship SUCCEEDS, so it failed precisely in the case it was written to confirm. The merged-tag sweep would have gone silent. It gated on `status = 'done'`, and with the lanes on a merged pull request lands the card on a delivery lane, so nothing would be tagged until a human finished dragging it. The archive sweep deliberately still gates on Done: a card in a delivery lane is in flight, while the tag is a statement about the pull request. `deployed` is renamed to `merged`, because that is the signal Studio has. Nothing reads a deployment; what moves a card here is GitHub reporting the pull request merged. Free today, a data migration the moment anyone enables the flag. The rebase kept both sides where #6544 had landed in between: `cardWorkLanded` decides WHETHER a card landed, `shippedLane` decides WHICH lane, and two tests written against the older reader shape were ported rather than dropped. Co-authored-by: Viktor Marinho <viktor@deco.cx>
decocms Bot
pushed a commit
that referenced
this pull request
Aug 27, 2026
PR: #6457 feat(task-board): delivery lanes behind a default-off org flag Bump type: minor - decocms (apps/api/package.json): 4.280.3 -> 4.281.0 - @decocms/native (apps/native/package.json): 4.280.3 -> 4.281.0 - @decocms/e2e (packages/e2e/package.json): 1.51.0 -> 1.52.0 - @decocms/shared (packages/shared/package.json): 0.60.1 -> 0.61.0 Deploy-Scope: both
This was referenced Aug 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this contribution about?
Adds three board lanes — Approved, Merged, Post-deploy Validation — between In Review and Done, for teams whose release process continues after the merge, gated by a new default-off org flag
delivery_lanes_enabled. Every automatic ship path (thereview-decisionauto-merge, the auto-merge retry, "Ship to production",prs-get's reconcile-on-view, and the merged sweep) now reads one function,shippedLane(flags), so "flag off = zero behaviour change" is a single tested fact rather than five call sites agreeing by luck.Along the way this fixes a latent bug:
prs-get'sstatus !== "done" && !== "archived"enumeration would have dragged a card resting in Post-deploy Validation back to Merged on every PR-modal open, so it andrun-reactions' delivered-failure relabel now compare by rank (LANE_RANK/movesForward, extracted intolanes.ts). Three related holes are closed in the same PR:REVIEW_CLOSING_STATUSESnow covers the new lanes (a run setting the ship lane otherwise escapedclosesOwnReviewand dropped the card out oflistItemsPendingReview, so its reviewers never ran),hasHumanRejectedDonetreats leaving the ship lane as the same veto as leavingdone, and the web ship-button gate accepts Approved so the lane isn't a UI dead end.Note
approvedis deliberately human-only here — reachable by drag, "Move to" and Jira mapping, never written by automation. Making automation write it requires widening seven sites together (listItemsPendingReview,review-sweeper,advanceToDoneIfMerged,retryAutoMergeIfApproved,claimInReviewSuperAgentSlot,conflict-reaction,isTaskHandedToHuman); any subset strands cards, so that is a follow-up.Adoption note — who wrote what
The branch is @fernandofrizzatti's and the authorship is preserved, but it sat behind 290 commits of
mainand I took it over to land it. The rebase had eight conflicted files and git folded my fixes into his single commit, so the diff does not separate his work from mine. Everything below the line is mine; everything above is his.The rebase. #6544 landed in the middle of the drift and touched the same lines in
reconcile-mergedandprs-get. Both meanings are kept:cardWorkLandeddecides whether a card's work landed (per repo, so a bounced PR doesn't strand it),shippedLanedecides which lane it lands on.tool-io.tswas regenerated rather than hand-merged.The e2e never passed. It asserted
expect(shipError).not.toMatch(...)on a value that isnullwhen the ship succeeds, and Bun throws on a non-string matcher argument — so it failed in exactly the case it was written to confirm. Now.then(() => "", (e) => String(e)).The merged-tag sweep would have gone silent. It gated on
status = 'done', but with the flag on a merged PR leaves the card on a delivery lane, so nothing would be tagged until a human finished dragging.TAGGABLE_MERGED_STATUSES(storage) andisTaggableMergedStatus(lanes.ts) now cover the lanes a merge can leave a card on. The archive sweep deliberately still gates on Done only, and the asymmetry is the point: a card in a delivery lane is still in flight, while the tag is a statement about the pull request.deployedis renamed tomerged(23 files + regenerated contracts). It's the signal Studio actually has — nothing reads a deployment; what moves the card is GitHub reporting the PR merged. The name is free to change today and becomes a data migration the moment any org enables the flag.Two of the tests were written against the pre-#6544 reader shape (
async () => true,[true]). Ported to{ state, merged }/PrLandingrather than deleted.How did you verify your code works?
New pure unit suites
lanes.test.ts(rank ordering,movesForwardrefusing the backward drag,shippedLaneresolving every falsy flag shape todone), plus new cases inconfig.test.ts(moveTargets,laneVisibilityincl. the "card stuck in a switched-off lane stays reachable" property) andreview-status.test.ts(laneCanShip). Per the repo's inversion rule the tests that encoded the old behaviour were inverted rather than appended to:reconcile-merged.test.tsnow asserts both flag-off →doneand flag-on → the ship lane, andupdate.test.ts'sclosesOwnReview"anywhere but Done/Archived" case now pins the delivery lanes as review-closing.Real-Postgres coverage in
archive-merged.integration.test.ts(a card parked in a delivery lane is never swept, via both the candidate query and the write path),human-rejected-done.integration.test.ts(leaving the ship lane is a veto; leaving a later lane is not), and two new cases for the widened tag sweep.The e2e now runs and passes in CI —
task-board-delivery-lanes.spec.ts, 3 tests, e2e-shard 2. That resolves the "unrun, worth a run before merge" caveat this description used to carry. Locally:bun run check0 type errors, api suite green against real Postgres,bun run lintat baseline,knipclean,fmt:checkclean. Every CI check on the branch is green.Screenshots/Demonstration
Not captured. The UI change is three additional board columns plus one settings toggle, both invisible until the flag is enabled.
How to Test
main(5 visible columns, Archived hidden) and that "Move to" and the task dialog's status dropdown offer no new lanes.ORGANIZATION_SETTINGS_UPDATE { flags: { delivery_lanes_enabled: true } }, or Settings → Review → "Show delivery lanes".In Review → Approved → Merged → Post-deploy Validation → Done; each move should persist and write astatus_changedtimeline entry.Migration Notes
None.
task_board_items.statusis plaintextwith no CHECK constraint (migration 126) and the existingstatus_changedactivity action is reused, so no schema change is needed. The generatedpackages/shared/src/tools/tool-io.tsdiff is committed — regenerate withbun run --cwd=apps/api generate:tool-contractsif you touch the status or flags schemas.Review Checklist